/*
 * Copyright 2018 requery.io
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.requery.android.sqlcipher

import android.database.Cursor
import io.requery.android.sqlite.BasePreparedStatement
import io.requery.android.sqlite.CursorResultSet
import io.requery.android.sqlite.SingleResultSet
import net.sqlcipher.database.SQLiteException
import net.sqlcipher.database.SQLiteStatement

import java.sql.ResultSet
import java.sql.SQLException
import java.sql.Statement

/**
 * [java.sql.PreparedStatement] implementation using Android's local SQLite database.
 */
internal class SqlCipherPreparedStatement @Throws(SQLException::class)
constructor(private val cipherConnection: SqlCipherConnection, sql: String, autoGeneratedKeys: Int)
    : BasePreparedStatement(cipherConnection, sql, autoGeneratedKeys) {

    private val statement: SQLiteStatement = cipherConnection.database.compileStatement(sql)
    private var cursor: Cursor? = null

    override fun bindNullOrString(index: Int, value: Any?) {
        if (value == null) {
            statement.bindNull(index)
            bindings?.add(null)
        } else {
            val string = value.toString()
            statement.bindString(index, string)
            bindings?.add(string)
        }
    }

    override fun bindLong(index: Int, value: Long) {
        statement.bindLong(index, value)
        bindings?.add(value)
    }

    override fun bindDouble(index: Int, value: Double) {
        statement.bindDouble(index, value)
        bindings?.add(value)
    }

    override fun bindBlob(index: Int, value: ByteArray?) {
        if (value == null) {
            statement.bindNull(index)
            bindings?.add(null)
        } else {
            statement.bindBlob(index, value)
            if (bindings != null) {
                bindBlobLiteral(index, value)
            }
        }
    }

    @Throws(SQLException::class)
    override fun close() {
        clearParameters()
        statement.close()
        cursor?.close()
        super.close()
    }

    override fun execute(sql: String, autoGeneratedKeys: Int): Boolean {
        throw UnsupportedOperationException()
    }

    override fun executeQuery(sql: String): ResultSet {
        throw UnsupportedOperationException()
    }

    override fun executeUpdate(sql: String, autoGeneratedKeys: Int): Int {
        throw UnsupportedOperationException()
    }

    override fun clearParameters() {
        statement.clearBindings()
        bindings?.clear()
    }

    @Throws(SQLException::class)
    override fun execute(): Boolean {
        try {
            statement.execute()
        } catch (e: SQLiteException) {
            SqlCipherConnection.throwSQLException(e)
        }

        return false
    }

    @Throws(SQLException::class)
    override fun executeQuery(): ResultSet? {
        try {
            val args = bindingsToArray()
            cursor = cipherConnection.database.rawQuery(sql, args)
            queryResult = CursorResultSet(this, cursor!!, false)
            return queryResult
        } catch (e: SQLiteException) {
            SqlCipherConnection.throwSQLException(e)
        }

        return null
    }

    @Throws(SQLException::class)
    override fun executeUpdate(): Int {
        if (autoGeneratedKeys == Statement.RETURN_GENERATED_KEYS) {
            try {
                val rowId = statement.executeInsert()
                insertResult = SingleResultSet(this, rowId)
                updateCount = 1
            } catch (e: SQLiteException) {
                SqlCipherConnection.throwSQLException(e)
            }

        } else {
            try {
                updateCount = statement.executeUpdateDelete()
            } catch (e: SQLiteException) {
                SqlCipherConnection.throwSQLException(e)
            }

        }
        return updateCount
    }
}
